#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QMessageBox>
#include <QFileDialog>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QTextCodec>
#include <QTime>
#include <QStringList>
#include <QStandardItemModel>
#include <QListWidgetItem>
#include <QScrollBar>
#include <QDropEvent>
#include <QDragEnterEvent>
#include <QMimeData>
#include <QUrl>

#include <iostream>
#include <fstream>

#include "storage.h"

MainWindow::MainWindow(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::MainWindow),
    m_searchCancelFlag(false)
{
    ui->setupUi(this);
    ui->progressBar->setVisible(false);
    ui->listWidget->setSelectionMode(QAbstractItemView::ContiguousSelection);

    m_storage = new Storage;

    connect(ui->btnAddFile, &QPushButton::clicked, this, &MainWindow::slotAddFile);
    connect(ui->btnSearch, &QPushButton::clicked, this, &MainWindow::search);
    connect(ui->ckbHidePrefix, &QCheckBox::toggled, this, &MainWindow::flushResult);
    connect(ui->listWidget, &QListWidget::itemSelectionChanged, this, &MainWindow::slotFileListSelectChanged);
    connect(ui->btnRemoveFile, &QPushButton::clicked, this, &MainWindow::slotRemoveFile);
    connect(ui->ckbPrettyDateTime, &QCheckBox::toggled, this, &MainWindow::flushFileList);
    connect(ui->btnSearchCancel, &QPushButton::clicked, this, &MainWindow::slotCancelSearch);

    setAcceptDrops(true);
}

MainWindow::~MainWindow()
{
    delete ui;
    delete m_storage;
}

class Progress {
public:
    Progress(QProgressBar * bar) : m_bar(bar), m_value(0) {
        bar->setValue(0);
        bar->setVisible(true);
        m_stack.clear();
    }
    ~Progress() {
        m_bar->setVisible(false);
    }
    void Push(int64_t cap, int callPack = 1) {
        m_stack.push_back({cap, 0, callPack, 0});
    }
    void Pop(){
        m_stack.pop_back();
    }
    void Inc(int incValue = 1) {
        auto & sub = m_stack.back();
        sub.value += incValue;
        Q_ASSERT(sub.value <= sub.cap);
        if (++sub.pack % sub.callPack == 0) {
            sub.pack = 0;
            float value = 0.0f, subTotal = 1.0f;
            for (auto i=0u; i<m_stack.size(); ++i) {
                auto v = (float)m_stack[i].value / (float)m_stack[i].cap * subTotal;
                value += v;
                subTotal = 1.0f / (float)m_stack[i].cap;
            }
            setValue((int)(value * 100.0f));
        }
    }

private:
    void setValue(int value) {
        if (m_value == value) { return; }
        m_bar->setValue(value);
        m_value = value;
        QApplication::processEvents();
    }

private:
    QProgressBar * m_bar;
    int m_value;
    struct SubProgress {
        int64_t cap;
        int64_t value;
        const int callPack;
        int pack;
    };
    std::vector<SubProgress> m_stack;
};

void MainWindow::slotAddFile(){

    // m_lines += loadFile("/home/liyuhui/Downloads/baidulog/DIAGNOSTIC-0-20191205072227.log");
    auto filenames = QFileDialog::getOpenFileNames(this);    
    loadFiles(filenames);

//    // QTime time; time.start();
//    progress.Push(filenames.size());
//    for(auto filename : filenames) {
//        loadFile(filename, progress);
//        progress.Inc();
//    }
//    progress.Pop();
//    // auto t = time.elapsed();
//    // QMessageBox::information(this, "", QString::number(t / 1000.0f));
//    m_storage->Rebuild();
//    flushUI();
}

void MainWindow::slotRemoveFile() {
    auto items = ui->listWidget->selectedItems();
    if (items.size() == 0) { return; }
    if (QMessageBox::StandardButton::Yes != QMessageBox::question(this, "Remove Files", QString("Remove this %1 file(s)?").arg(items.size()))) { return; }
    for (auto item : items) {
        auto filename = item->data(Qt::ToolTipRole).toString();
        m_storage->RemoveFile(filename);
        delete item;
    }
    m_storage->Rebuild();
    flushUI();
}

void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
    auto urls = event->mimeData()->urls();
    for (const auto & url : urls) {
        if (url.isLocalFile()) {
            event->acceptProposedAction();
            return;
        }
    }
    event->ignore();
}

void MainWindow::dropEvent(QDropEvent *event) {
    QStringList filenames;
    auto urls = event->mimeData()->urls();
    for (const auto & url : urls) {
        if (url.isLocalFile()) {
            filenames.append(url.path());
        }
    }
    loadFiles(filenames);
}

void MainWindow::showEvent(QShowEvent *event) {
    QDialog::showEvent(event);
    QMetaObject::invokeMethod(this, "afterWindowShown", Qt::ConnectionType::QueuedConnection);
}

void MainWindow::afterWindowShown() {
    this->slotAddFile();
}

void MainWindow::loadFiles(QStringList & filenames) {
    if (filenames.size() == 0) { return; }

    ui->groupBox->setEnabled(false);
    ui->groupBox_2->setEnabled(false);

    // QTime time; time.start();
    Progress progress(ui->progressBar);
    progress.Push(filenames.size());
    for(auto filename : filenames) {
        loadFile(filename, progress);
        progress.Inc();
    }
    progress.Pop();
    // auto t = time.elapsed(); QMessageBox::information(this, "", QString::number(t / 1000.0f));
    m_storage->Rebuild();
    flushUI();

    ui->groupBox->setEnabled(true);
    ui->groupBox_2->setEnabled(true);

}

void MainWindow::loadFile(const QString & filename, Progress & progress) {
    QFile file(filename);
    if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return; }
    auto fileIndex = m_storage->AddFile(filename); if (fileIndex == nullptr) { file.close(); return; }
    QString line; qint64 readBytes, posStart = 0, posEnd = 0;
    progress.Push(file.size(), 100);
    while(Utils::readLine(file, line, readBytes)) {
        posEnd += readBytes;
        m_storage->RegLine(fileIndex, line, posStart, posEnd);
        posStart = posEnd;
        progress.Inc(readBytes);
    }
    file.close();
    progress.Pop();
}

struct FillComboBox{
    static void call(QComboBox * cb, const QList<QString> & data){
        cb->clear();
        QStringList stringList(data);
        stringList.sort();
        cb->addItems(stringList);
        cb->setCurrentText("");
    }
};

void MainWindow::flushFileList() {
    ui->listWidget->clear();
    auto files = m_storage->GetFileIndexs().keys(); files.sort();
    for (auto filename : files) {
        QListWidgetItem * item = new QListWidgetItem;
        item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
        item->setCheckState(Qt::CheckState::Checked);
        item->setData(Qt::ToolTipRole, filename);
        auto text = QFileInfo(filename).fileName();
        if (ui->ckbPrettyDateTime->checkState() == Qt::Checked) {
            text = prettyDateTime(text);
        }
        item->setText(text);
        ui->listWidget->addItem(item);
    }
}

void MainWindow::flushUI(){
    flushFileList();
    FillComboBox::call(ui->cbLevel, m_storage->GetLevels().toList());
    FillComboBox::call(ui->cbTime, m_storage->GetTimes().toList());
    FillComboBox::call(ui->cbThreadId, m_storage->GetThreadIds().toList());
    FillComboBox::call(ui->cbCate1, m_storage->GetCate1s().toList());
    FillComboBox::call(ui->cbCate2, m_storage->GetCate2s().toList());
    FillComboBox::call(ui->cbCate3, m_storage->GetCate3s().toList());
    ui->txtLog->clear();
    ui->labTotolLine->setText(QString("Total Lines: %1").arg(commaNumber(m_storage->GetLineCount())));
}

void MainWindow::slotFileListSelectChanged() {
    ui->btnRemoveFile->setEnabled(ui->listWidget->selectedItems().size() > 0);
}

void MainWindow::flushResult() {
    search();
}

void MainWindow::search() {
    auto level = ui->cbLevel->currentText();
    auto time = ui->cbTime->currentText();
    auto threadId = ui->cbThreadId->currentText();
    auto cate1 = ui->cbCate1->currentText();
    auto cate2 = ui->cbCate2->currentText();
    auto cate3 = ui->cbCate3->currentText();

    QStringList filenames;
    for (auto i=0; i<ui->listWidget->count(); ++i) {
        auto item = ui->listWidget->item(i);
        if (item->checkState() == Qt::CheckState::Checked) {
            QString filename = item->data(Qt::ToolTipRole).toString();
            filenames.append(filename);
        }
    }
    if (filenames.empty()) { return; }
    filenames.sort();

    ui->txtLog->clear(); // static QString content; content.clear();
    ui->groupBox->setEnabled(false);
    ui->formLayout->setEnabled(false);
    ui->btnSearch->setEnabled(false);
    ui->ckbHidePrefix->setEnabled(false);
    ui->btnSearchCancel->setEnabled(true);

    Progress progress(ui->progressBar);
    progress.Push(filenames.size());

    int64_t searchResultCount = 0;
    for (auto filename : filenames) {
        if (m_searchCancelFlag) { break; }
        QSet<Line> setSearchResult;
        m_storage->Search(filename, level, time, threadId, cate1, cate2, cate3, setSearchResult);
        auto searchResult = setSearchResult.toList();
        qSort(searchResult);
        if (searchResult.size()) {
            // searchResultCount += searchResult.size();
            QFile file(filename);
            if(file.open(QIODevice::ReadOnly | QIODevice::Text)) {
                std::string buffer;
                progress.Push(searchResult.size(), 200);
                for(const auto & range : searchResult){
                    if (m_searchCancelFlag) { break; }
                    progress.Inc();
                    auto bResult = file.seek(range.start); if (!bResult) { Q_ASSERT(false); break; }
                    buffer.reserve(range.size);   // the end of buffer is must be a '\n', and will be replace to a '\0'
                    char * ptr = (char *)buffer.c_str();
                    qint64 readBytes = file.read(ptr, range.size);
                    if (readBytes != range.size || ptr[range.size-1] != '\n') { Q_ASSERT(false); break; }
                    ptr[range.size-1] = '\0';
                    if (ui->ckbHidePrefix->checkState() == Qt::CheckState::Checked){
                        QStringRef items[Storage::ITEM_COUNT], desc;
                        QString text = QString::fromUtf8(ptr);
                        if (!Storage::ParseLine(items, desc, text)){ continue; }
                        static QString strDesc; strDesc.clear(); desc.appendTo(&strDesc);
                        ui->txtLog->append(strDesc);
                        // content.append(strDesc); content.append('\n');
                    } else {
                        ui->txtLog->append(ptr);
                        // content.append(ptr); content.append('\n');
                    }
                    ++searchResultCount;
                }
                progress.Pop();
                file.close();
            } else { Q_ASSERT(false);  }
        }

        progress.Inc();
    }
    // ui->txtLog->setText(content);
    ui->txtLog->verticalScrollBar()->setValue(0);
    ui->lab_count->setText(QString("Search Lines: %1").arg(commaNumber(searchResultCount)));

    progress.Pop();

    ui->btnSearchCancel->setEnabled(false);
    ui->formLayout->setEnabled(true);
    ui->btnSearch->setEnabled(true);
    ui->ckbHidePrefix->setEnabled(true);
    ui->groupBox->setEnabled(true);

    if (m_searchCancelFlag) { m_searchCancelFlag = false; }
}

void MainWindow::slotCancelSearch() {
    m_searchCancelFlag = true;
}
